﻿Imports System.Net
Imports System.Text
Imports System.Web
Imports System.IO
Imports System.Xml
Imports System.Runtime.Serialization

Namespace Scraping
    Partial Class TwitterClient
        Implements ITwitterClient
        ''' <summary>
        ''' Twitterのユーザーに関する処理中のデータを保持します。
        ''' </summary>
        ''' <remarks></remarks>
        Private Class FriendshipBundle
            Private _result As Boolean
            Public Property Result() As Boolean
                Get
                    Return _result
                End Get
                Set(ByVal value As Boolean)
                    _result = value
                End Set
            End Property

            Private _userID As Long
            Public Property UserID() As Long
                Get
                    Return _userID
                End Get
                Set(ByVal value As Long)
                    _userID = value
                End Set
            End Property

            Private _userName As String
            Public Property UserName() As String
                Get
                    Return _userName
                End Get
                Set(ByVal value As String)
                    _userName = value
                End Set
            End Property
        End Class

        ''' <summary>
        ''' Twitterのユーザーページから処理に必要な情報を取得します。
        ''' </summary>
        ''' <param name="userName">ユーザー名</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function GetFriendshipBundle(ByVal userName As String) As FriendshipBundle
            Dim bundle As New FriendshipBundle()
            bundle.UserName = userName
            Dim url As String = String.Format("{0}/{1}", My.Resources.TwitterUrl, userName)
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterGetHeaders(Nothing, _cookies)
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.OK
                        res.LoadCookies(_cookies)
                        Dim xmlRes As XmlDocument = res.GetResponseAsXHtml()
                        Dim xnm As XmlNamespaceManager = xmlRes.GetXHtmlNameSpaceManager("x")
                        bundle.UserID = CLng(xmlRes.SelectSingleNode("//x:div[starts-with(@id,'follow_actions_')]", xnm).Attributes("id").Value.Substring(15))
                        bundle.Result = True
                    Case Else
                        bundle.Result = False
                End Select
            End Using
            Return bundle
        End Function

        ''' <summary>
        ''' ユーザーをフォローした結果として返されるJSONを格納します。
        ''' </summary>
        ''' <remarks></remarks>
        <DataContract()> _
        Private Class CreateFriendshipJsonResult
            Private _result As String
            <DataMember(Name:="result")> _
            Public Property Result() As String
                Get
                    Return _result
                End Get
                Set(ByVal value As String)
                    _result = value
                End Set
            End Property
            Private _notice As String
            <DataMember(Name:="notice")> _
            Public Property Notice() As String
                Get
                    Return _notice
                End Get
                Set(ByVal value As String)
                    _notice = value
                End Set
            End Property
            Private _success As Boolean
            <DataMember(Name:="success")> _
            Public Property Success() As Boolean
                Get
                    Return _success
                End Get
                Set(ByVal value As Boolean)
                    _success = value
                End Set
            End Property
        End Class

        ''' <summary>
        ''' ユーザー名を指定してユーザーをフォローします。
        ''' </summary>
        ''' <param name="userName">フォローするユーザー名</param>
        ''' <returns>フォローに成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function CreateFriendship(ByVal userName As String) As Boolean Implements ITwitterClient.CreateFriendship
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As FriendshipBundle = GetFriendshipBundle(userName)
            If (bundle.Result = True) Then
                CreateFriendShipPost(bundle)
            End If
            Return bundle.Result
        End Function

        ''' <summary>
        ''' TwitterUserを指定してユーザーをフォローします。
        ''' </summary>
        ''' <param name="user">フォローするTwitterUser</param>
        ''' <returns>フォローに成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function CreateFriendship(ByVal user As TwitterUser) As Boolean Implements ITwitterClient.CreateFriendship
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As New FriendshipBundle()
            bundle.UserID = user.UserID
            bundle.UserName = user.UserName
            CreateFriendShipPost(bundle)
            Return bundle.Result
        End Function

        ''' <summary>
        ''' Twitterにフォローのリクエストをポストして結果を取得します。
        ''' </summary>
        ''' <param name="bundle">処理中のデータを保持するFriendshipBundle</param>
        ''' <remarks></remarks>
        Private Sub CreateFriendShipPost(ByVal bundle As FriendshipBundle)
            Dim url As String = String.Format("{0}{1}{2}", My.Resources.TwitterUrl, My.Resources.TwitterCreateFriendshipPath, bundle.UserID)
            Dim urlReferer As String = String.Format("{0}/{1}", My.Resources.TwitterUrl, bundle.UserName)
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterXHRPostHeaders(urlReferer, _cookies)
            Dim sbPost As New StringBuilder()
            sbPost.AppendFormat("{0}={1}&", HttpUtility.UrlEncode(_authenticityTokenFiledName), HttpUtility.UrlEncode(_authenticityToken))
            sbPost.AppendFormat("{0}={1}", "twttr", "true")
            Dim strPost As String = sbPost.ToString()
            req.PostString(strPost)
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.OK
                        res.LoadCookies(_cookies)
                        Dim jsonRsult As CreateFriendshipJsonResult = res.GetResponseAsJson(Of CreateFriendshipJsonResult)()
                        If (jsonRsult.Success) Then
                            bundle.Result = True
                        Else
                            bundle.Result = False
                        End If
                    Case Else
                        bundle.Result = False
                End Select
            End Using
        End Sub

        ''' <summary>
        ''' ユーザー名を指定してフォロー解除します。
        ''' </summary>
        ''' <param name="userName">フォロー解除するユーザー名</param>
        ''' <returns>フォロー解除に成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function DestroyFriendship(ByVal userName As String) As Boolean Implements ITwitterClient.DestroyFriendship
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As FriendshipBundle = GetFriendshipBundle(userName)
            If (bundle.Result = True) Then
                DestroyFriendShipPost(bundle)
            End If
            Return bundle.Result
        End Function

        ''' <summary>
        ''' TwitterUserを指定してユーザーをフォロー解除します。
        ''' </summary>
        ''' <param name="user">フォロー解除するTwitterUser</param>
        ''' <returns>フォロー解除に成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function DestroyFriendship(ByVal user As TwitterUser) As Boolean Implements ITwitterClient.DestroyFriendship
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As New FriendshipBundle()
            bundle.UserID = user.UserID
            bundle.UserName = user.UserName
            DestroyFriendShipPost(bundle)
            Return bundle.Result
        End Function

        ''' <summary>
        ''' Twitterにフォロー解除のリクエストをポストして結果を取得します。
        ''' </summary>
        ''' <param name="bundle"></param>
        ''' <remarks></remarks>
        Private Sub DestroyFriendShipPost(ByVal bundle As FriendshipBundle)
            Dim url As String = String.Format("{0}{1}{2}", My.Resources.TwitterUrl, My.Resources.TwitterDestroyFriendshipPath, bundle.UserID)
            Dim urlReferer As String = String.Format("{0}/{1}", My.Resources.TwitterUrl, bundle.UserName)
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterXHRPostHeaders(urlReferer, _cookies)
            Dim sbPost As New StringBuilder()
            sbPost.AppendFormat("{0}={1}&", HttpUtility.UrlEncode(_authenticityTokenFiledName), HttpUtility.UrlEncode(_authenticityToken))
            sbPost.AppendFormat("{0}={1}", "twttr", "true")
            Dim strPost As String = sbPost.ToString()
            req.PostString(strPost)
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.OK
                        res.LoadCookies(_cookies)
                        Dim jsonRsult As CreateFriendshipJsonResult = res.GetResponseAsJson(Of CreateFriendshipJsonResult)()
                        If jsonRsult.Success Then
                            bundle.Result = True
                        Else
                            bundle.Result = False
                        End If
                    Case Else
                        bundle.Result = False
                End Select
            End Using
        End Sub

        ''' <summary>
        ''' ユーザー名を指定してブロックします。
        ''' </summary>
        ''' <param name="userName">ブロックするユーザー名</param>
        ''' <returns>ブロックに成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function CreateBlock(ByVal userName As String) As Boolean Implements ITwitterClient.CreateBlock
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As FriendshipBundle = GetFriendshipBundle(userName)
            If (bundle.Result = True) Then
                CreateBlockPost(bundle)
            End If
            Return bundle.Result
        End Function

        ''' <summary>
        ''' TwitterUserを指定してユーザーをブロックします。
        ''' </summary>
        ''' <param name="user">ブロックするTwitterUser</param>
        ''' <returns>ブロックに成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function CreateBlock(ByVal user As TwitterUser) As Boolean Implements ITwitterClient.CreateBlock
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As New FriendshipBundle()
            bundle.UserID = user.UserID
            bundle.UserName = user.UserName
            CreateBlockPost(bundle)
            Return bundle.Result
        End Function

        ''' <summary>
        ''' Twitterにブロックのリクエストをポストして結果を取得します。
        ''' </summary>
        ''' <param name="bundle"></param>
        ''' <remarks></remarks>
        Private Sub CreateBlockPost(ByVal bundle As FriendshipBundle)
            Dim url As String = String.Format("{0}{1}{2}", My.Resources.TwitterUrl, My.Resources.TwitterCreateBlockPath, bundle.UserID)
            Dim urlReferer As String = String.Format("{0}{1}{2}", My.Resources.TwitterUrl, My.Resources.TwitterConfirmBlockPath, bundle.UserID)
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterPostHeaders(urlReferer, _cookies)
            Dim sbPost As New StringBuilder()
            sbPost.AppendFormat("{0}={1}&", HttpUtility.UrlEncode(_authenticityTokenFiledName), HttpUtility.UrlEncode(_authenticityToken))
            sbPost.AppendFormat("{0}={1}", "commit", HttpUtility.UrlEncode("はい、このユーザーをブロックします"))
            Dim strPost As String = sbPost.ToString()
            req.PostString(strPost)
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.Found
                        res.LoadCookies(_cookies)
                        bundle.Result = True
                    Case Else
                        bundle.Result = False
                End Select
            End Using
        End Sub

        ''' <summary>
        ''' ユーザー名を指定してブロック解除します。
        ''' </summary>
        ''' <param name="userName">ブロック解除するユーザー名</param>
        ''' <returns>ブロック解除に成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function DestroyBlock(ByVal userName As String) As Boolean Implements ITwitterClient.DestroyBlock
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As FriendshipBundle = GetFriendshipBundle(userName)
            If (bundle.Result = True) Then
                DestroyBlockPost(bundle)
            End If
            Return bundle.Result
        End Function

        ''' <summary>
        ''' TwitterUserを指定してユーザーをブロック解除します。
        ''' </summary>
        ''' <param name="user">ブロック解除するTwitterUser</param>
        ''' <returns>ブロック解除に成功すればTrue、失敗すればFalse</returns>
        ''' <remarks></remarks>
        Public Function DestroyBlock(ByVal user As TwitterUser) As Boolean Implements ITwitterClient.DestroyBlock
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If

            Dim bundle As New FriendshipBundle()
            bundle.UserID = user.UserID
            bundle.UserName = user.UserName
            DestroyBlockPost(bundle)
            Return bundle.Result
        End Function

        ''' <summary>
        ''' Twitterにブロック解除のリクエストをポストして結果を取得します。
        ''' </summary>
        ''' <param name="bundle"></param>
        ''' <remarks></remarks>
        Private Sub DestroyBlockPost(ByVal bundle As FriendshipBundle)
            Dim url As String = String.Format("{0}{1}{2}", My.Resources.TwitterUrl, My.Resources.TwitterDestroyBlockPath, bundle.UserID)
            Dim urlReferer As String = String.Format("{0}/{1}", My.Resources.TwitterUrl, bundle.UserName)
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterPostHeaders(urlReferer, _cookies)
            Dim sbPost As New StringBuilder()
            sbPost.AppendFormat("{0}={1}", HttpUtility.UrlEncode(_authenticityTokenFiledName), HttpUtility.UrlEncode(_authenticityToken))
            Dim strPost As String = sbPost.ToString()
            req.PostString(strPost)
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.Found
                        res.LoadCookies(_cookies)
                        bundle.Result = True
                    Case Else
                        bundle.Result = False
                End Select
            End Using
        End Sub
    End Class
End Namespace